import psutil
import os, subprocess
from src.mod import run
from src.core.log import log
from src.mod import Resource
from src.mod import safe_remove
from src.core.variables.info import sof
from src.core.variables.constant import CONF
from datetime import datetime


class Nginx:
    """nginx管理类"""
    def __init__(self) -> None:
        self.version = ""
        self.install = False
        self.status = False
        self.component = {}
        self.pkg = Resource()
        self.pkg_path = self.pkg.nginx.path
        self.__check_install()
        if not self.install:
            self.__compile()
            if sof.fm_nginx:
                self.__init_conf()
                self.__clear_temp()
        if sof.fm_nginx:
            self.status: bool = self.__get_status()
            log.INFO(f'初始化阶段检测fm_nginx状态：{self.status}')
            self.__get_component()
            self.prefix = self.component['--prefix']
            if not self.status:
                log.INFO('启动fm_nginx...')
                self.start()
            self.status: bool = self.__get_status()
            log.INFO(f'初始化阶段检测fm_nginx状态：{self.status}')
            if not self.status:
                log.CRITICAL('启动fm_nginx失败！')
            self.conf_path = f"{self.prefix}/conf/nginx.conf"
            self.conf_d_dir = f"{self.prefix}/conf/fm_conf"

    def conf_template(self, ):
        """nginx配置模板"""
        with open(f'{CONF.PATH.ABS}/conf/template.conf', 'r') as file:
            return ' '.join(file.readlines())

    def __check_install(
        self,
    ):
        """检查是否编译安装"""
        result = ""
        if os.path.exists(self.pkg.nginx.install_path):
            try:
                result = run(f"{self.pkg.nginx.install_path}/sbin/nginx -v")
                if result == "nginx version: nginx/1.28.0 (fm_nginx_1.28.0)":
                    self.install = True
                    self.version = "1.28.0"
                    log.INFO(f"中间件 渔翁发现自编译的 'fm_nginx'")
                    sof.fm_nginx = True
                    return
            except Exception as e:
                log.ERROR(f"获取fm_nginx版本失败: {e}")
        log.INFO(f"未检测到渔翁自编译的fm_nginx！")

    def __get_status(self) -> bool:
        """获取nginx状态"""
        for proc in psutil.process_iter(['name']):
            try:
                if proc.info['name'] == 'nginx':
                    return True
            except (psutil.NoSuchProcess, psutil.AccessDenied):
                # 进程已退出或无权限，跳过
                continue
        return False

    def __get_component(
        self,
    ):
        """获取nginx组件"""
        result = run(f"{self.pkg.nginx.install_path}/sbin/nginx -V").split("arguments:")[1].split(" ")[1:]
        for i in result:
            self.component[i.split("=")[0]] = i.split("=")[1]

    def __compile(self, configure_param={}):
        """编译安装nginx（带实时日志监测）"""
        """编译安装nginx"""
        log.INFO(f"渔翁自编译：fm_nginx_1.28.0(基于nginx-1.28.0)开始编译...")
        if not os.path.exists(self.pkg_path):
            raise FileNotFoundError(f"压缩包不存在: {self.pkg_path}")
        target_dir = f"{CONF.PATH.ABS}/data/temp/fm_nginx"
        os.makedirs(target_dir, exist_ok=True, mode=0o755)
        run(f"tar -zxvf {self.pkg_path} -C {target_dir}")
        contents = os.listdir(target_dir)
        if not contents:
            raise RuntimeError(f"解压后的目录为空: {target_dir}")
        source_dir = os.path.join(target_dir, contents[0])
        configure_str = " ".join(
            f"{k}={v}" for k, v in self.pkg.nginx.default_configure_param.items()
        ) + " ".join(f"{k}={v}" for k, v in configure_param.items())
        # 编译命令
        cmd = f"""
        cd {source_dir}
        ./configure {configure_str}
        make -j$(nproc)
        make install
        """
        # 创建日志文件
        compile_log_path = f'{CONF.PATH.ABS}/logs/{datetime.now().strftime("%Y%m%d_%H%M%S")}_nginx_compile.log'
        compile_log_file = open(compile_log_path, 'w')
        log.INFO(f"编译日志将输出到文件: {compile_log_path}")
        
        # 执行编译
        process = subprocess.Popen(
            cmd, shell=True, stdout=compile_log_file, stderr=subprocess.STDOUT, text=True
        )
        stdout, stderr = process.communicate()
        compile_log_file.close()
        
        if process.returncode != 0:
            log.ERROR(f"编译失败，详细信息请查看日志文件: {compile_log_path}")
            raise RuntimeError(f"编译失败，详细信息请查看日志文件: {compile_log_path}")
        log.INFO(f"渔翁自编译：fm_nginx_1.28.0(基于nginx-1.28.0)编译成功！")
        sof.fm_nginx = True
        self.install = True


    def __init_conf(self):
        """初始化nginx.conf"""
        original_path = f'{CONF.PATH.ABS}/conf/nginx.conf'
        new_path = f'{CONF.PATH.ABS}/data/software/fm_nginx/conf/nginx.conf'
        with open(original_path, 'r') as f_in:
            modified_content = ''
            for line in f_in.readlines():
                modified_content += line.replace('@fm', CONF.PATH.ABS) \
                    .replace('@port', str(CONF.LOCAL_PORT)) \
                    .replace('@url', f'http://{CONF.LOCAL_IP}:{CONF.LOCAL_PORT-1}')
        with open(new_path, 'w') as f_out:
            f_out.write(modified_content)

    def __clear_temp(self, ):
        """清理缓存"""
        safe_remove(f"{CONF.PATH.ABS}/data/temp/fm_nginx", is_dir=True)
        log.DEBUG(f'删除编译缓存。')

    def __run(self, cmd: str=''):
        result = run(f'{self.prefix}/sbin/nginx {cmd}')
        log.DEBUG(f'fm_nginx {cmd} 结果反馈：{result}')
        return result
    
    def start(self):
        if self.__get_status():
            return
        return self.__run()
    
    def stop(self,):
        # 强制退出nginx进程
        if self.__get_status():
            return self.__run("-s quit")
        return
    
    def reload(self, ):
        # 平滑重启nginx进程
        if self.__get_status():
            return self.__run("-s reload")
        else:
            return self.__run()
    
    def check(self, ):
        # 检查nginx配置文件是否正确
        if not self.__get_status():
            self.start()
        return self.__run("-t")
    
    def change_include(self, file_path: str, operation: str) -> bool:
        """在http块内添加/删除/注释include指令及对应文件"""
        if operation not in ['add', 'remove', 'cancel']:
            raise ValueError(f"无效的操作: {operation}，可选值: add/remove/cancel")
        include_line = f"include {file_path};"
        commented_line = f"# include {file_path};"
        with open(self.conf_path, 'r', encoding='utf-8') as f:
            content = f.read()
        lines = content.split('\n')
        new_lines = []
        in_http_block = False
        http_block_depth = 0
        found = False
        found_commented = False
        for i, line in enumerate(lines):
            stripped_line = line.strip()
            if stripped_line.startswith('http {'):
                in_http_block = True
                http_block_depth = 1
                new_lines.append(line)
                continue
            elif in_http_block:
                if '{' in line:
                    http_block_depth += 1
                if '}' in line:
                    http_block_depth -= 1
                    if http_block_depth == 0:
                        in_http_block = False
                        # 在http块结束前插入include指令
                        if not found and not found_commented and operation in ['add', 'cancel']:
                            if operation == 'add':
                                new_lines.append('    ' + include_line)
                                log.INFO(f"在http块内添加include指令: {include_line}")
                            elif operation == 'cancel':
                                new_lines.append('    ' + commented_line)
                                log.INFO(f"在http块内添加注释的include指令: {commented_line}")
            if in_http_block:
                if stripped_line == include_line:
                    found = True
                    if operation == 'add':
                        new_lines.append(line)
                    elif operation == 'remove':
                        continue  # 跳过删除的行
                    elif operation == 'cancel':
                        new_lines.append('#' + line.lstrip())
                elif stripped_line == commented_line:
                    found_commented = True
                    if operation == 'add':
                        uncommented_line = line.replace('#', '', 1).lstrip()
                        new_lines.append(uncommented_line)
                    elif operation == 'remove':
                        continue  # 跳过删除的行
                    elif operation == 'cancel':
                        new_lines.append(line)
                else:
                    new_lines.append(line)
                if stripped_line and not stripped_line.startswith('#'):
                    insert_position = len(new_lines)
            else:
                new_lines.append(line)
        if not any('http {' in line for line in lines) and operation in ['add', 'cancel']:
            # 在文件末尾添加http块
            new_lines.append('http {')
            if operation == 'add':
                new_lines.append('    ' + include_line)
            elif operation == 'cancel':
                new_lines.append('    ' + commented_line)
            new_lines.append('}')
            log.INFO(f"创建http块并添加include指令: {include_line}")
        new_content = '\n'.join(new_lines)
        with open(self.conf_path, 'w', encoding='utf-8') as f:
            f.write(new_content)
        if operation == 'add':
            if found:
                log.INFO(f"include指令已存在且已取消注释: {include_line}")
            elif found_commented:
                log.INFO(f"include指令已取消注释: {include_line}")
            else:
                log.INFO(f"在http块内添加include指令: {include_line}")
        elif operation == 'remove':
            safe_remove(file_path)
            log.INFO(f"删除http块内的include指令及配置文件: {file_path}")
        elif operation == 'cancel':
            if found:
                log.INFO(f"注释include指令: {include_line}")
            elif found_commented:
                log.INFO(f"include指令已处于注释状态: {include_line}")
            else:
                log.INFO(f"在http块内添加注释的include指令: {commented_line}")
        log.INFO(f"修改nginx配置文件成功！")
        return True

    def get_include_status(self, file_path: str) -> bool:
        """获取include指令状态"""
        with open(self.conf_path, 'r', encoding='utf-8') as f:
            content = f.read()
        include_line = f"include {file_path};"
        return include_line in content
    
    def get_include_annotiation_status(self, file_path: str) -> bool:
        """获取include指令注释状态"""
        with open(self.conf_path, 'r', encoding='utf-8') as f:
            content = f.read()
        commented_line = f"# include {file_path};"
        return commented_line in content

